# Copyright The IETF Trust 2014-2020, All Rights Reserved
# -*- coding: utf-8 -*-


import os
import collections
import io

from importlib import import_module

import debug                            # pyflakes:ignore

from django.core.management.base import AppCommand
from django.db import models
from django.template import Template, Context
from django.utils import timezone

from tastypie.resources import ModelResource


resource_head_template = """# Copyright The IETF Trust {{date}}, All Rights Reserved
# -*- coding: utf-8 -*-
# Generated by the makeresources management command {{date}}


from tastypie.resources import ModelResource
from tastypie.fields import ToManyField                 # pyflakes:ignore
from tastypie.constants import ALL, ALL_WITH_RELATIONS  # pyflakes:ignore
from tastypie.cache import SimpleCache

from ietf import api
from ietf.api import ToOneField                         # pyflakes:ignore

from {{app}}.models import *                            # pyflakes:ignore
"""

resource_class_template = """{% autoescape off %}
{% for model in models %}{% for import in model.imports %}{% if import.module != app_label %}
from ietf.{{ import.module }}.resources import {% for name in import.names %}{% if not forloop.first %}, {%endif%}{{name}}Resource{% endfor %}{% endif %}{% endfor %}
class {{model.name}}Resource(ModelResource):{% if model.foreign_keys %}{% for fk in model.foreign_keys %}
    {{fk.name|ljust:"16"}} = ToOneField({{fk.rmodel_name}}, '{{fk.name}}'{% if fk.field.null %}, null=True{% endif %}){% endfor %}{% endif %}{% if model.m2m_keys %}{% for fk in model.m2m_keys %}
    {{fk.name|ljust:"16"}} = ToManyField({{fk.rmodel_name}}, '{{fk.name}}', null=True){% endfor %}{% endif %}
    class Meta:
        queryset = {{model.name}}.objects.all()
        serializer = api.Serializer()
        cache = SimpleCache()
        {% if model.rn_comment %}#resource_name = '{{model.resource_name}}'{% else %}resource_name = '{{model.resource_name}}'{% endif %}
        ordering = ['{{model.pk_name}}', ]
        filtering = { {% for name in model.plain_names %}
            "{{ name }}": ALL,{%endfor%}{% for name in model.fk_names%}
            "{{ name }}": ALL_WITH_RELATIONS,{%endfor%}{% for name in model.m2m_names %}
            "{{ name }}": ALL_WITH_RELATIONS,{%endfor%}
        }
api.{{app_label}}.register({{model.name}}Resource())
{% endfor %}{% endautoescape %}"""

def render(template, dictionary):
    template = Template(template, None, None)
    context = Context(dictionary)
    return template.render(context)

class Command(AppCommand):

    def handle_app_config(self, app, **options):
        # dotted path to app
        if app.name:
            print("\nInspecting %s .." % app.name)
            resource_file_path = os.path.join(app.path, "resources.py")

            app_models = app.get_models()

            app_resources = {}
            if os.path.exists(resource_file_path):
                resources = import_module("%s.resources" % app.name)
                for n,v in resources.__dict__.items():
                    if issubclass(type(v), type(ModelResource)):
                        app_resources[n] = v

            missing_resources = []
            for m in app_models:
                model_name = m.__name__
                rclass_name = model_name + "Resource"
                if not rclass_name in app_resources:
                    missing_resources.append((m, rclass_name))

            if missing_resources:
                print("Updating resources.py for %s" % app.name)
                with io.open(resource_file_path, "a") as rfile:
                    info = dict(
                        app=app.name,
                        app_label=app.label,
                        date=timezone.now()
                    )
                    new_models = {}
                    for model, rclass_name in missing_resources:
                        model_name = model.__name__
                        resource_name = model.__name__.lower()
                        imports = collections.defaultdict(lambda: collections.defaultdict(list))
                        print("Adding resource class for %s" % model_name)
                        foreign_keys = []
                        plain_names = []
                        fk_names = []
                        m2m_names = []
                        pk_name = model._meta.pk.name
                        #debug.pprint('dir(model)')
                        for field in model._meta.fields:
                            if isinstance(field, (models.ForeignKey, models.OneToOneField)):
                                #debug.show('field.name')
                                #debug.pprint('dir(field.remote_field.to)')
                                #exit()
                                rel_app=field.remote_field.model._meta.app_label
                                rel_model_name=field.remote_field.model.__name__
                                if rel_model_name == model_name:
                                    # foreign key to self class -- quote
                                    # the rmodel_name
                                    rmodel_name="'%s.resources.%sResource'" % (app.name, rel_model_name)
                                else:
                                    rmodel_name=rel_model_name+"Resource"
                                foreign_keys.append(dict(
                                    field=field,
                                    name=field.name,
                                    app=rel_app,
                                    module=rel_app.split('.')[-1],
                                    model=field.remote_field.model,
                                    model_name=rel_model_name,
                                    rmodel_name=rmodel_name,
                                    resource_name=field.remote_field.model.__name__.lower(),
                                    ))
                                imports[rel_app]["module"] = rel_app
                                imports[rel_app]["names"].append(rel_model_name)
                                fk_names.append(field.name)
                            else:
                                plain_names.append(field.name)
                        m2m_keys = []
                        for field in model._meta.many_to_many:
                                #debug.show('field.name')
                                #debug.pprint('dir(field.remote_field.model)')
                                #exit()
                                rel_app=field.remote_field.model._meta.app_label
                                rel_model_name=field.remote_field.model.__name__
                                if rel_model_name == model_name:
                                    # foreign key to self class -- quote
                                    # the rmodel_name
                                    rmodel_name="'%s.resources.%sResource'" % (app.name, rel_model_name)
                                else:
                                    rmodel_name=rel_model_name+"Resource"
                                m2m_keys.append(dict(
                                    field=field,
                                    name=field.name,
                                    app=rel_app,
                                    module=rel_app.split('.')[-1],
                                    model=field.remote_field.model,
                                    model_name=rel_model_name,
                                    rmodel_name=rmodel_name,
                                    resource_name=field.remote_field.model.__name__.lower(),
                                    ))
                                imports[rel_app]["module"] = rel_app
                                imports[rel_app]["names"].append(rel_model_name)
                                m2m_names.append(field.name)
                        # some special import cases
                        if "auth" in imports:
                            imports["auth"]["module"] = 'utils'
                        if "contenttypes" in imports:
                            imports["contenttypes"]["module"] = 'utils'
                        for k in imports:
                            imports[k]["names"] = set(imports[k]["names"])
                        new_models[model_name] = dict(
                            app=app.name.split('.')[-1],
                            model=model,
                            fields=model._meta.fields,
                            m2m_fields=model._meta.many_to_many,
                            name=model_name,
                            imports=[ v for k,v in list(imports.items()) ],
                            foreign_keys=foreign_keys,
                            m2m_keys=m2m_keys,
                            resource_name=resource_name,
                            plain_names=plain_names,
                            fk_names=fk_names,
                            m2m_names=m2m_names,
                            pk_name=pk_name,
                            rn_comment=False if resource_name.endswith('resource') else True,
                        )

                    # Sort resources according to internal FK reference depth
                    new_model_list = []
                    # Write out classes with FKs to classes in the same module
                    # lower or equal to 'internal_fk_count_limit.  Start out
                    # by writing only leaf classes, then increase the limit if
                    # needed:
                    internal_fk_count_limit = 0
                    while len(new_models) > 0:
                        list_len = len(new_models)
                        #debug.show('len(new_models)')
                        keys = list(new_models.keys())
                        for model_name in keys:
                            internal_fk_count = 0
                            for fk in new_models[model_name]["foreign_keys"]+new_models[model_name]["m2m_keys"]:
                                #debug.say("if statement comparison on:")
                                #debug.show('fk["model_name"]')
                                #debug.show('model_name')
                                #debug.say('if fk["model_name"] in new_models and not fk["model_name"] == model_name:')
                                if fk["model_name"] in new_models and not fk["model_name"] == model_name:
                                    #print("Not a leaf model: %s: found fk to %s" % (model_name, fk["model"]))
                                    internal_fk_count += 1
                            if internal_fk_count <= internal_fk_count_limit:
                                #print("Ordered: "+model_name)
                                new_model_list.append(new_models[model_name])
                                del new_models[model_name]
                        if list_len == len(new_models):
                            #debug.show('list_len, len(new_models)')
                            print("Circular FK dependencies -- cannot order resource classes")
                            if internal_fk_count_limit < list_len:
                                print("Attempting a partial ordering ...")
                                internal_fk_count_limit += 1
                            else:
                                print("Failed also with partial ordering, writing resource classes without ordering")
                                new_model_list = [ v for k,v in list(new_models.items()) ]
                                break

                    if rfile.tell() == 0:
                        print("Writing resource file head")
                        rfile.write(render(resource_head_template, info))
                    else:
                        print("\nNOTE: Not writing resource file head.\nYou may have to update the import from %s.models" % app.name)

                    info.update(dict(models=new_model_list))
                    rfile.write(render(resource_class_template, info))
            else:
                print("  nothing to do for %s" % app.name)
